package com.huixue.cn.ztej.utils;

import java.io.File;
import java.io.FileReader;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * A reloadable properties loader. This class maintain a daemon thread to
 * monitor the file for modifying. You can also register a properties notifier
 * to it to handle something when config file was modified
 * <dl>
 * <dt><b>Creation:</b></dt>
 * <dd>Dec 20, 2012 3:31:25 PM</dd>
 * </dl>
 * 
 * @author mayanjun
 * @version 1.0
 */
public class Config {

	private Config() {
		this.prop = new Properties();
		String path = null;
		try {
			path = this
					.getClass()
					.getClassLoader()
					.getResource(
							Config.class.getPackage().getName().replace('.', '/')
									+ "/config.properties").toURI().getPath();
		} catch (Exception e) {
			e.printStackTrace();
		}
		this.notifiers = new HashSet<Config.PropertyNotifier>();
		this.lock = new ReentrantReadWriteLock();
		this.configFile = new File(path);
		this.loadProp();
		Thread t = new Thread(new FileModifiedMonitor(this.configFile.lastModified()));
		t.setDaemon(true);
		t.start();
	}

	private final static Config CONFIG = new Config();
	private final Properties prop;
	private final File configFile;
	private final ReadWriteLock lock;

	private final Set<PropertyNotifier> notifiers;

	public static Config getConfig() {
		return CONFIG;
	}

	public String getAsString(String key) {
		String ret = "";
		try {
			this.lock.readLock().lock();
			ret = this.prop.getProperty(key);
		} finally {
			this.lock.readLock().unlock();
		}
		return ret;
	}

	public int getAsInteger(String key) {
		int ret = 0;
		try {
			this.lock.readLock().lock();
			String v = this.prop.getProperty(key);
			if (v != null) {
				try {
					ret = Integer.parseInt(v);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} finally {
			this.lock.readLock().unlock();
		}
		return ret;
	}

	private void loadProp() {
		try {
			this.lock.writeLock().lock();
			this.prop.load(new FileReader(configFile));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			this.lock.writeLock().unlock();
		}
	}

	public static String getString(String key) {
		return Config.getConfig().getAsString(key);
	}

	public static boolean getBoolean(String key) {
		String v = Config.getConfig().getAsString(key);
		if (v != null) {
			if ("true".equalsIgnoreCase(v))
				return true;
		}
		return false;
	}

	public static int getInt(String key) {
		return Config.getConfig().getAsInteger(key);
	}

	public static void registerPropertyNotifier(PropertyNotifier notifier) {
		Config.getConfig().notifiers.add(notifier);
	}

	// Utility inner class
	private class FileModifiedMonitor implements Runnable {
		private long lastModifiedTime;

		public FileModifiedMonitor(long lastModifiedTime) {
			this.lastModifiedTime = lastModifiedTime;
		}

		@Override
		public void run() {
			while (true) {
				long lmt = Config.this.configFile.lastModified();
				if (lmt > this.lastModifiedTime) { // File changed
					Config.this.loadProp();
					this.lastModifiedTime = lmt;
					// fire event
					fireEvent(new PropertyEvent(Config.this, lmt));
				}
				try {
					Thread.sleep(1000 * 5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		private void fireEvent(PropertyEvent event) {
			Set<PropertyNotifier> notifiers = Config.this.notifiers;
			if (notifiers != null && notifiers.size() > 0) {
				for (PropertyNotifier notifier : notifiers) {
					notifier.notify(event);
				}
			}
		}
	}

	public static interface PropertyNotifier {
		public void notify(PropertyEvent event);
	}

	public static class PropertyEvent {

		private final long modifiedTime;
		private final Config config;

		public PropertyEvent(Config config, long modifiedTime) {
			this.modifiedTime = modifiedTime;
			this.config = config;
		}

		public long getModifiedTime() {
			return modifiedTime;
		}

		public Config getConfig() {
			return config;
		}
	}

	// /////////////// convenient methods /////////////////
	public static String getSystemName() {
		return getString("system.name");
	}

	public static String getVersionName() {
		return getString("version.name");
	}

	public static String getVersionCode() {
		return getString("version.code");
	}
}
